// 局部变量
let func = function() {
  let a = 1;
  alert(a); // 输出: 1
}; 
func();
alert(a); // 输出：Uncaught ReferenceError: a is not defined

let func = function() {
  let a = 1;
  return function() {
    a++;
    alert(a);
  }
};
let f = func();
f(); // 输出：2
f(); // 输出：3
f(); // 输出：4
f(); // 输出：5

// 面向对象设计
let extent = function() {
  let value = 0;
  return {
    call() {
      value++;
      console.log(value);
    }
  }
};
let extent = extent();
extent.call(); // 输出：1
extent.call(); // 输出：2
extent.call(); // 输出：3 

let extent = {
  value: 0,
  call() {
    this.value++;
    console.log(this.value);
  }
};
extent.call(); // 输出：1
extent.call(); // 输出：2
extent.call(); // 输出：3

/**
 * 命令模式
 * 把请求封装为对象
 * 从而分离请求的发起者和请求的接收者（执行者）之间的耦合关系
 * */ 
let Tv = {
  open() {
    console.log('打开电视机');
  },
  close() {
    console.log('关上电视机');
  }
};
let OpenTvCommand = function(receiver) {
  this.receiver = receiver;
};
OpenTvCommand.prototype.execute = function() {
  this.receiver.open(); // 执行命令，打开电视机
};
OpenTvCommand.prototype.undo = function() {
  this.receiver.close(); // 撤销命令，关闭电视机
};
let setCommand = function(command) {
  document.getElementById('execute').onclick = function() {
    command.execute(); // 输出：打开电视机
  }
  document.getElementById('undo').onclick = function() {
    command.undo(); // 输出：关闭电视机
  }
};
setCommand(new OpenTvCommand(Tv));

let Tv = {
  open: function() {
    console.log('打开电视机');
  },
  close: function() {
    console.log('关上电视机');
  }
};
let createCommand = function(receiver) {
  let execute = function() {
    return receiver.open(); // 执行命令，打开电视机
  }
  let undo = function() {
    return receiver.close(); // 执行命令，关闭电视机
  }
  return {
    execute,
    undo
  }
}; 
let setCommand = function(command) {
  document.getElementById('execute').onclick = function() {
    command.execute(); // 输出：打开电视机
  }
  document.getElementById('undo').onclick = function() {
    command.undo(); // 输出：关闭电视机
  }
};
setCommand(createCommand(Tv));

/**
 * 高阶函数
 * 函数可以作为参数被传递
 * 函数可以作为返回值输出
*/
// 函数可以作为参数被传递
// 抽离出一部分容易变化的业务逻辑，把这部分业务逻辑放在函数参数中 -> 可以分离业务代码中变化与不变的部分
// 回调函数
let getUserInfo = function(userId, callback) {
  $.ajax('http://xxx.com/getUserInfo?' + userId, function(data) {
    if (typeof callback === 'function') {
      callback(data);
    }
  });
}
getUserInfo(13157, function(data) {
  alert (data.userName);
}); 

//从小到大排列
[1, 4, 3].sort((a, b) => {
  return a - b; 
});
// 输出: [ 1, 3, 4 ]
//从大到小排列
[1, 4, 3].sort((a, b) => {
 return b - a;
});
// 输出: [ 4, 3, 1 ]

// 函数可以作为返回值输出
// 判断数据的类型
let isType = function(type) {
  return function(obj) {
    return Object.prototype.toString.call( obj ) === '[object '+ type +']';
  }
}; 
let isString = isType('String');
let isArray = isType('Array');
let isNumber = isType('Number');
console.log(isArray([1, 2, 3])); // 输出：true 


let getSingle = fn => {
  let ret;
  return function() {
    return ret || (ret = fn.apply(this, arguments));
  };
}; 
let getScript = getSingle(() => {
  return document.createElement('script');
});
let script1 = getScript();
let script2 = getScript();
alert(script1 === script2); // 输出：true
 
// 高阶函数实现AOP--面向切面编程
Function.prototype.before = function(beforefn) {
  // 保存原函数的引用
  let __self = this;
  // 返回包含了原函数和新函数的"代理"函数
  return function() {
    // 执行新函数，修正 this
    beforefn.apply(this, arguments);
    // 执行原函数
    return __self.apply(this, arguments);
  }
};
Function.prototype.after = function(afterfn) {
  let __self = this;
  return function() {
    let ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
};
let func = function() {
  console.log(2);
};
func = func.before(function() {
  console.log(1);
}).after(function() {
  console.log(3);
});
func(); // 1 2 3

/**
 * 函数柯里化
 * 1. 一个 currying 的函数首先会接收一些参数
 * 2. 接收了这些参数之后，该函数并不会立即求值，而是继续返回另外一个函数
 * 3. 刚才传入的参数在函数形成的闭包中被保存起来
 * 4. 待到函数被真正需要求值的时候，之前传入的所有参数都会被一次性用于求值
 * */ 
let monthlyCost = 0;
let cost = function(money) {
  monthlyCost += money;
};
cost(100); // 第 1 天开销
cost(200); // 第 2 天开销
cost(300); // 第 3 天开销
 //cost( 700 ); // 第 30 天开销
alert(monthlyCost); // 输出：600

let cost = (function() {
  let args = [];
  return function() {
    if (arguments.length === 0) {
      let money = 0;
      for (let i = 0, l = args.length; i < l; i++) {
        money += args[i];
      }
      return money;
    } else {
      [].push.apply(args, arguments);
    }
  }
})();
cost(100); // 未真正求值
cost(200); // 未真正求值
cost(300); // 未真正求值
console.log(cost()); // 求值并输出：600

// currying化函数
let currying = function(fn) {
  let args = [];
  return function() {
    if (arguments.length === 0) {
      return fn.apply(this, args);
    } else {
      [].push.apply(args, arguments);
      return arguments.callee;
    }
  }
};
let cost = (function() {
  let money = 0;
  return function() {
    for ( let i = 0, l = arguments.length; i < l; i++ ) {
      money += arguments[i];
    }
    return money;
  }
})();
let cost = currying(cost); // 转化成 currying 函数
cost(100); // 未真正求值
cost(200); // 未真正求值
cost(300); // 未真正求值
alert(cost()); // 求值并输出：600

function throttle(func, delay) {
  let run = true
  return function() {
    if(!run) {
      return
    }
    run = false
    setTimeout(() => {
      func.apply(this, arguments)
      run  = true
    }, delay);
  }
}